<?php
/**
 *
 *
 */
namespace Yz\modules\Router;

use Yz,
    Yz_Array,
    Yz_Hash,
    Yz_String,
    I18N;

class Router_Default
    extends \Yz_Router_Abstract
    implements \Yz_Router_Interface
{

    const DEFAULT_MODULE = 'index';
    const ERROR_MODULE = 'error';

    /**
     *  @var string
     */
    protected  $path;

    /** The list of references to all queried modules (by selector)
     *  @var array[Module_Interface]
     */
    protected $modulesList = array();

    /** The reference to current module
     *  @var Module_Interface
     */
    protected $module;
    /** The reference to current controller
     *  @var Controller_Interface
     */
    protected $controller;
    /** The selector of current module
     *  @var string
     */
    protected $moduleSelector;
    /** The selector of current language
     *  @var string
     */
    protected $languageSelector;
    /** The selector of current controller
     *  @var string
     */
    protected $controllerSelector;


    /********************************
     *      Public methods          *
     *******************************/


    /**
     *  @param string|null $path null means use the path from HTTP Request
     *  @return Yz_Controller_Interface
     */
    public function routeRequest()
    {
        debug();
        $this->path = $this->getRequest()->getPath();
        $this
            ->identifyWebPrefix()
            ->doRoute()
        ;
        return $this;
    }

    /**
     *  @param string $path null means use the path from HTTP Request
     *  @return Yz_Controller_Interface
     */
    public function route( $path, Yz_Array $args=null )
    {
        debug();
        $path = Yz_String::test($path);
        $this->path = $path;
        $this->arguments = $args;
        return $this
            ->doRoute()
        ;
    }


    /**
     *  @return chainable
     */
    public function sendResponse()
    {
        $view = $this->controller->getView();
        $rs = $this->getResponse();
        $rs->setView($view)->send();
        return $this;
    }

    /** get the list of modules which were queried by the router
     *      in the course of processing the request
     *
     *  @return Yz_Hash
     */
    public function getModulesList()
    {
        return new Yz_Hash($this->modulesList);
    }

    /** get the language as it was present in arguments
     *  @return string
     */
    public function getLanguageSelector()
    {
        return $this->languageSelector;
    }

    /** get the selector of the module, i.e. the part of path which was used
     *      to identify the current module
     *
     *  @return string
     */
    public function getModuleSelector()
    {
        return $this->moduleSelector;
    }

    /** get the selector of the controller, i.e. the part of path which was used
     *      to identify the current controller,
     *      as supplied to the module.selectController()
     *
     *  @return string
     */
    public function getControllerSelector()
    {
        return $this->controllerSelector;
    }

    /** whether the admin module is active
     *  @todo the admin module can have a different selector:
     *      check if a module is the admin module (in identifymodule)
     *  @return bool
     */
    public function isAdmin()
    {
        return array_key_exists('admin', $this->modulesList);
    }


    /***************************
     *     Protected methods   *
     ***************************/


    protected function identifyRequestMethod()
    {
        null === $this->isPOST
            and $this->isPOST = $this->getRequest()->isPOST();
        return $this;
    }

    protected function identifyWebPrefix()
    {
        $path =& $this->path;
        $rq = $this->getRequest();
        $scriptName = $rq->getScriptName();

        // otherwise it's a very non-standard case :)
        if ("" !== $scriptName ) {

            // the engine's web-root is the directory path to its entry script
            $webRoot = dirname($scriptName);
            "/" === $webRoot or $webRoot .= '/';
            $this->webRoot = $webRoot;

            $prefix = $scriptName . '/';
            // the full script name is present in the request path => rewrite apparently off
            if ( 0 === strncasecmp( $prefix, $path . "/", strlen($prefix) )) {
                debug("rewrite apparently OFF");
                $this->webPrefix = $prefix;
                $path = substr( $path, strlen($prefix) );
            } else {
                debug("rewrite apparently ON");
                $this->webPrefix = $webRoot;
                if ("/" === $webRoot) {
                    debug("WebRoot: / (root)");
                } else {

                    // remove the web-root from effective path
                    if ( 0 === strncasecmp( $webRoot, $path . "/", strlen($webRoot) )) {
                        $path = substr( $path, strlen($webRoot) );
                    }
                }
            }

            debug("ScriptName: '$scriptName', webRoot: '$webRoot', prefix: '$prefix'");
        }

        return $this;
    }

    /**
     *  working on Arguments array
     *
     */
    protected function identifyLanguage()
    {
        debug();
        Yz::initI18N();
        $args = $this->arguments;
        if (! $args->isEmpty()) {
            $selector = $args[0];
            debug("Testing for language '$selector'");
            if ( I18N::isInstalledLanguage($selector) ) {
                I18N::setCurrentLanguage($selector);
                $this->languageSelector = $selector;

                $args->shift();
            }
        }
        return $this;
    }

    /**
     *  working on Arguments array
     *
     */
    protected function identifyModule()
    {
        debug();
        $args = $this->arguments;
        if ($args->isEmpty()) {
            $moduleName = static::DEFAULT_MODULE;
        } else {
            $moduleName = $args[0];
        }
        $module = Yz::selectModule($moduleName);
        if (! ($module instanceof \Yz_Module_WebModule_Interface)) {
            throw new \Yz_Exception("Module $module not accessible");
        }
        $this->moduleSelector = $args->shift();
        $this->module = $module;
        $this->modulesList[$moduleName] = $module;
        return $this;
    }

    /**
     *  working on Arguments array
     *
     */
    protected function identifyController()
    {
        debug();
        $args = $this->arguments;
        $module = $this->module;
        $controller = null;
        if (! $args->isEmpty()) {
            $controller = $module->selectController($args[0]);
        }
        if (null === $controller) {
            $controller = $module->selectDefaultController();
        } else {
            $this->controllerSelector = $args->shift();
        }
        $this->controller = $controller;
        return $this;
    }

    protected function executeController()
    {
        $this->controller->go($this);
        return $this;
        // OLD
        try {
            return $this->controller->go($this);
        } catch (\Yz_Exception $ex) {
            debug($ex->toString(true));
            $this->exception = $ex;
            return Yz::selectModule(static::ERROR_MODULE)
                ->selectDefaultController()
                ->go($this)
                ;
        }
    }

    protected function pathToArray()
    {
        debug();
        $path = $this->path;
        $appendArgs = $this->arguments;

        // this is faster than preg_split by /+ ?
        $args = new Yz_Array( explode("/", $path) );
        // do not use native array_filter because it drops zeroes, too
        $this->arguments = $args->filter();
        if (null !== $appendArgs) {
            $this->arguments->append($appendArgs);
        }
        return $this;
    }

    protected function doRoute()
    {
        debug("Route path: '{$this->path}'");
        $this->module = null;
        $this->moduleSelector = null;
        $this->controller = null;
        $this->controllerSelector = null;
        $this->languageSelector = null;
        try {
            return $this
                ->identifyRequestMethod()
                ->pathToArray()
                ->identifyLanguage()
                ->identifyModule()
                ->identifyController()
                ->executeController()
                ;
        } catch (\Yz_Exception $ex) {
            debug($ex->toString(true));
            $this->exception = $ex;
            $this->controller = Yz::selectModule(static::ERROR_MODULE)
                ->selectDefaultController()
                ->go($this)
                ;
            return $this;
        }
    }

}
